﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using AmiEmuLib.Host.HostInput;
using System.Diagnostics;
using AmiEmuLib.AmigaConfiguration;
using log4net;
using TomanuExtensions;

namespace AmiEmuLib.Configuration.EventBindingConfig
{
    /// <summary>
    /// Host events that generate amiga event.
    /// </summary>
    [DebuggerDisplay("Shortuct = {Shortcut}")]
    public class HostEventsCombination
    {
        /// <summary>
        /// Logger.
        /// </summary>
        private static readonly ILog Logger =
            LogManager.GetLogger(typeof(HostEventsCombination).GetParentFullName());

        /// <summary>
        /// Maximum number of events in combination.
        /// </summary>
        public const int MAX_EVENTS_COUNT = 4;

        /// <summary>
        /// Host events combination.
        /// </summary>
        private List<HostEvent> m_combination = new List<HostEvent>();

        /// <summary>
        /// Read only.
        /// </summary>
        public readonly bool ReadOnly;

        /// <summary>
        /// Constructor.
        /// </summary>
        internal HostEventsCombination()
        {
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="a_source"></param>
        internal HostEventsCombination(HostEventsCombination a_source)
        {
            ReadOnly = false;
            m_combination.AddRange(a_source.Combination);
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="a_reader"></param>
        /// <param name="a_read_only"></param>
        internal HostEventsCombination(XmlReader a_reader, bool a_read_only)
        {
            ReadOnly = a_read_only;

            a_reader.ReadStartElement(GetType().Name);

            while (a_reader.IsEmptyElement)
            {
                string event_name = a_reader.GetAttribute("Name");
                HostEvent ev = HostEventsList.GetEvent(event_name);
                if (ev != null)
                    m_combination.Add(ev);
                else
                {
                    Logger.ErrorFormat(
                        "Cannot find host event defined in combination: {0}", event_name);
                }
                a_reader.MoveToNextElement("HostEvent");
            }

            a_reader.ReadEndElement();

            if (!m_combination.Select(c => c.Name).Unique())
            {
                Logger.Error("Host events are not uniqe in combination.");
                m_combination = m_combination.Distinct().ToList();
            }

            if (m_combination.Count > MAX_EVENTS_COUNT)
            {
                Logger.Error("Too much host events in combination");
                m_combination = m_combination.Take(MAX_EVENTS_COUNT).ToList();
            }
        }

        /// <summary>
        /// Write xml.
        /// </summary>
        /// <param name="a_writer"></param>
        public void WriteXml(XmlWriter a_writer)
        {
            a_writer.WriteStartElement("HostEventsCombination");

            foreach (var host_event in m_combination)
                host_event.WriteXml(a_writer);

            a_writer.WriteEndElement();
        }

        /// <summary>
        /// Host events combination.
        /// </summary>
        public IEnumerable<HostEvent> Combination
        {
            get
            {
                return m_combination;
            }
        }

        /// <summary>
        // Update combination.
        /// </summary>
        /// <param name="a_index"></param>
        /// <param name="a_name"></param>
        internal bool UpdateCombination(IEnumerable<HostEvent> a_combination)
        {
            if (ReadOnly)
            {
                Logger.Error("Combination is readonly.");
                return false;
            }

            if (a_combination == null)
            {
                m_combination.Clear();
                return true;
            }

            if (!a_combination.Unique())
            {
                Logger.Warn("Combination is not unique");
                a_combination = a_combination.Distinct();
            }

            if (a_combination.Count() > MAX_EVENTS_COUNT)
            {
                Logger.Warn("Too many elements in combination");
                a_combination = a_combination.Take(MAX_EVENTS_COUNT);
            }

            if (a_combination.Any(ev => ev.IgnoreWhenMapping))
            {
                Logger.Warn("HostEventsCombination.UpdateCombination - Some host events with IgnoreWhenMapping");
                a_combination = m_combination.Where(ev => !ev.IgnoreWhenMapping);
            }

            m_combination.Clear();
            m_combination.AddRange(a_combination);

            return true;
        }

        /// <summary>
        /// Get textual shortcut combination.
        /// </summary>
        /// <param name="a_events"></param>
        /// <returns></returns>
        public static string GetShortcut(IEnumerable<HostEvent> a_events)
        {
            Func<HostEvent, int> order1 = (ev) =>
            {
                if (HostEventsList.KeyboardEvents.Events.Contains(ev))
                    return 1;
                else
                    return 2;
            };

            Func<HostEvent, int> order2 = (ev) =>
            {
                // Alt, Shift, Ctrl
                if ((ev.ID >= 160) && (ev.ID <= 165))
                    return 1;
                // Windows
                if ((ev.ID == 91) || (ev.ID == 92))
                    return 2;

                return 3;
            };

            var sorted1 = a_events.OrderBy(ev => order1(ev)).
                ThenBy(ev => order2(ev)).ThenBy(ev => ev.ID);

            return sorted1.Aggregate("",
                (acc, ev) => (acc == "") ? ev.DisplayName : (acc + " + " + ev.DisplayName));
        }

        /// <summary>
        /// Get textual shortcut combination.
        /// </summary>
        public string Shortcut
        {
            get
            {
                return GetShortcut(m_combination);
            }
        }
    }
}
